package com.unicloud.medical.oss.common;

import com.unicloud.medical.oss.common.enums.ErrorEnum;
import com.unicloud.medical.oss.common.model.DownloadObject;
import com.unicloud.medical.oss.common.model.FileMetaData;
import com.unicloud.medical.oss.common.model.ObjectInfo;
import com.unicloud.medical.oss.common.model.request.*;
import com.unicloud.medical.oss.common.util.CommonUtil;
import com.unicloud.medical.oss.common.util.FileUtil;
import com.unisound.medical.common.exception.BusinessException;
import com.unisound.medical.common.util.AssertUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.FastByteArrayOutputStream;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.zip.ZipOutputStream;

@Slf4j
public abstract class AbstractOSSService implements OSSService {
    protected String buildObjectName(String folder, String fileName) {
        if (folder.endsWith("/")) return folder + fileName;
        return folder + "/" + fileName;
    }

    protected String getObjectName(String url, String bucketName) {
        int bucketNameIndex = url.lastIndexOf(bucketName + "/");
        if (bucketNameIndex == -1) return null;
        int substringIndex = bucketNameIndex + bucketName.length() + 1;
        return url.substring(substringIndex);
    }

    @Override
    public String accessUrl(String bucketName, String objectName) {
        StringBuilder builder = new StringBuilder();
        builder.append(StringUtils.isNotEmpty(proxyEndpoint()) ? proxyEndpoint() : endpoint())
                .append("/")
                .append(bucketName)
                .append("/")
                .append(objectName);
        return builder.toString();
    }

    @Override
    public Boolean bucketExist(BucketExistRequest request) {
        return bucketExist(request.getBucketName());
    }

    @Override
    public Boolean createBucket(CreateBucketRequest request) {
        return createBucket(request.getBucketName());
    }

    @Override
    public Boolean createDefaultPolicyBucket(String bucketName) {
        Boolean bucketExists = bucketExist(bucketName);
        if (!bucketExists) {
            Boolean createBucket = createBucket(bucketName);
            AssertUtil.assertTrue(BooleanUtils.isTrue(createBucket), ErrorEnum.CREATE_BUCKET_FAIL);
        }

        setDefaultBucketPolicy(bucketName);
        return true;
    }

    @Override
    public Boolean createDefaultPolicyBucket(CreateBucketRequest request) {
        return createDefaultPolicyBucket(request.getBucketName());
    }

    @Override
    public Boolean setDefaultBucketPolicy(String bucketName) {
        return setBucketPolicy(bucketName, defaultBucketPolicy(bucketName));
    }

    @Override
    public String uploadObject(InnerUploadObjectRequest request) {
        return uploadObject(request.getBucketName(), request.getObjectName(), request.getFile());
    }

    @Override
    public List<String> uploadObjects(String bucketName, String folder, List<FileMetaData> files) {
        AssertUtil.assertTrue(CollectionUtils.isNotEmpty(files), ErrorEnum.FILES_INVALID);
        try {
            List<String> urls = new ArrayList<>();
            for (FileMetaData file : files) {
                String objectName = buildObjectName(folder, file.getFileName());
                String url = uploadObject(bucketName, objectName, file);
                if (StringUtils.isNotEmpty(url)) {
                    urls.add(url);
                }
            }

            AssertUtil.assertTrue(CollectionUtils.isNotEmpty(urls), ErrorEnum.UPLOAD_FILES_FAIL);
            return urls;
        } catch (Exception e) {
            log.error("uploadObjects error. cause={}", e.getMessage());
            throw new BusinessException(ErrorEnum.UPLOAD_FILES_FAIL);
        }
    }

    @Override
    public List<String> uploadObjects(InnerUploadObjectsRequest request) {
        return uploadObjects(request.getBucketName(), request.getFolder(), request.getFiles());
    }

    @Override
    public String uploadLocalObject(UploadLocalObjectRequest request) {
        boolean success = uploadLocalObject(request.getBucketName(), request.getObjectName(), request.getFileName());
        AssertUtil.assertTrue(success, ErrorEnum.UPLOAD_LOCAL_FILE_FAIL);
        return accessUrl(request.getBucketName(), request.getObjectName());
    }

    @Override
    public List<String> uploadLocalObjects(UploadLocalObjectsRequest request) {
        List<String> fileNames = request.getFileNames();
        AssertUtil.assertTrue(CollectionUtils.isNotEmpty(fileNames), ErrorEnum.FILE_NAMES_INVALID);

        List<String> urls = new ArrayList<>();
        for (String fileName : request.getFileNames()) {
            String objectNameFileName = FileUtil.getFileName(fileName);
            AssertUtil.assertTrue(StringUtils.isNotEmpty(objectNameFileName), ErrorEnum.FILE_NAME_INVALID);
            String objectName = buildObjectName(request.getFolder(), objectNameFileName);
            Boolean success = uploadLocalObject(request.getBucketName(), objectName, fileName);
            if (BooleanUtils.isTrue(success)) {
                urls.add(accessUrl(request.getBucketName(), objectName));
            }
        }
        AssertUtil.assertTrue(CollectionUtils.isNotEmpty(urls), ErrorEnum.UPLOAD_LOCAL_FILES_FAIL);
        return urls;
    }

    @Override
    public DownloadObject downloadObject(String bucketName, String objectName) {
        if (StringUtils.isEmpty(bucketName) || StringUtils.isEmpty(objectName)) return null;
        try (InputStream inputStream = getObject(bucketName, objectName)) {
            if (Objects.isNull(inputStream)) return null;

            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
                while ((len = inputStream.read(buf)) != -1) {
                    os.write(buf, 0, len);
                }
                os.flush();

                return new DownloadObject("application/octet-stream", objectName, os.toByteArray());
            }
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public DownloadObject downloadObject(DownloadObjectRequest request) {
        return downloadObject(request.getBucketName(), request.getObjectName());
    }

    @Override
    public DownloadObject downloadObjects(String bucketName, String folder, String fileName) {
        // 列出文件夹下的所有文件
        List<ObjectInfo> objectInfos = listObjects(bucketName, folder);
        AssertUtil.assertTrue(CollectionUtils.isNotEmpty(objectInfos), ErrorEnum.FILE_NOT_FOUND_IN_FOLDER);

        // 创建压缩文件
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
            // 读取并写入每个文件到压缩文件
            for (ObjectInfo objectInfo : objectInfos) {
                String objectName = objectInfo.getName();
                // 获取文件输入流
                try (InputStream inputStream = getObject(bucketName, objectInfo.getName())) {
                    CommonUtil.zip(FileUtil.findFileName(objectName), inputStream, zipOutputStream);
                }
            }
        } catch (Exception e) {
            throw new BusinessException(ErrorEnum.FILE_DOWNLOAD_FAIL_IN_FOLDER);
        }

        return new DownloadObject("application/zip", fileName + ".zip", outputStream.toByteArray());
    }

    @Override
    public DownloadObject downloadObjects(DownloadObjectsRequest request) {
        return downloadObjects(request.getBucketName(), request.getFolder(), request.getFileName());
    }

    @Override
    public String removeObject(RemoveObjectRequest request) {
        String objectName = getObjectName(request.getUrl(), request.getBucketName());
        AssertUtil.assertTrue(StringUtils.isNotEmpty(objectName), ErrorEnum.INVALID_URL_OR_BUCKET);

        removeObject(request.getBucketName(), objectName);
        return objectName;
    }

    @Override
    public String defaultBucketPolicy(String bucketName) {
        return "{\n" +
                "    \"Statement\": [{\n" +
                "            \"Action\": [\n" +
                "                \"s3:GetBucketLocation\",\n" +
                "                \"s3:ListBucket\"\n" +
                "            ],\n" +
                "            \"Effect\": \"Allow\",\n" +
                "            \"Principal\": \"*\",\n" +
                "            \"Resource\": \"arn:aws:s3:::" + bucketName + "\"\n" +
                "        }, {\n" +
                "            \"Action\": \"s3:GetObject\",\n" +
                "            \"Effect\": \"Allow\",\n" +
                "            \"Principal\": \"*\",\n" +
                "            \"Resource\": \"arn:aws:s3:::" + bucketName + "/*\"\n" +
                "        }\n" +
                "    ],\n" +
                "    \"Version\": \"2012-10-17\"\n" +
                "}\n";
    }

    @Override
    public Boolean removeBucket(DeleteBucketRequest request) {
        return removeBucket(request.getBucketName());
    }

    @Override
    public List<ObjectInfo> listAllObjects(ListAllObjectsCurrBucketRequest request) {
        return listAllObjects(request.getBucketName());
    }

    @Override
    public List<ObjectInfo> listObjects(ListAllObjectsCurrBucketAndFolderRequest request) {
        return listObjects(request.getBucketName(), request.getFolder());
    }

    @Override
    public List<String> removeObjects(RemoveObjectsRequest request) {
        List<String> objectNames = request.getUrls().stream()
                .map(url -> getObjectName(url, request.getBucketName()))
                .filter(objectName -> Objects.nonNull(objectName))
                .collect(Collectors.toList());
        removeObjects(request.getBucketName(), objectNames);
        return objectNames;
    }

    @Override
    public List<String> removeObjects(RemoveObjectsCurrBucketAndFolderRequest request) {
        List<ObjectInfo> objectInfos = listObjects(request.getBucketName(), request.getFolder());
        if (CollectionUtils.isEmpty(objectInfos)) return null;

        List<String> objectNames = objectInfos.stream().map(objectInfo -> objectInfo.getName()).collect(Collectors.toList());
        removeObjects(request.getBucketName(), objectNames);
        return objectNames;
    }

    @Override
    public String copyObject(CopyObjectRequest request) {
        return copyObject(request.getBucketName(), request.getSourceObjectName(), request.getTargetObjectName());
    }

    @Override
    public String signedUrl(SignedUrlRequest request) {
        return signedUrl(request.getBucketName(), request.getObjectName());
    }

    @Override
    public List<String> signedUrls(String bucketName, List<String> objectNames) {
        AssertUtil.assertTrue(CollectionUtils.isNotEmpty(objectNames), ErrorEnum.OBJECT_NAMES_INVALID);
        return objectNames.stream()
                .map(objectName -> signedUrl(bucketName, objectName))
                .collect(Collectors.toList());
    }

    @Override
    public List<String> signedUrls(SignedUrlsRequest request) {
        return signedUrls(request.getBucketName(), request.getObjectNames());
    }
}
